
#include <BGridLayout>
#include <BHBoxLayout>
#include <BVBoxLayout>
#include <BFile>
#include <BReadWrite>
#include <BPainter>
#include <BStyle>
#include <BFont>
#include <BMessageBox>
#include <BMouseEvent>
#include <BMenu>

#include "StringUtils.h"
#include "ScriptBrowser.h"

ScriptBrowser::ScriptBrowser(BWidget* parent) : BTextEdit(parent)
{
	BString name = "zym", age = 33, height = 172;
	_hoveredItem = 0;

	ScriptFormat* format = new ScriptFormat();
	this->setFormat(format);

	connect(this, Signal_AssetChanged, &ScriptBrowser::slotAssetChanged);
	connect(this, Signal_Flushed, &ScriptBrowser::slotTextEditFlushed);

}
ScriptBrowser::~ScriptBrowser()
{
	if (_script)
		disconnect(_script);
}

void ScriptBrowser::freshDocument()
{
	this->clear();
	if (_script.empty())
		return;
	const BExpHolderArray& exps = _script->sequence();
	int level = 0;
	BString text;
	for (int i = 0; i < exps.size(); i++)
	{
		const BExp* exp = exps[i];
		text.reset();
		text.append('\t', level);
		switch (exp->syntax)
		{
		case Syntax_None:
			text << "jump @" << (int)exp->value;
			break;
		case Syntax_Include:
			text << "include <" << exp->name << '>';
			break;
		case Syntax_Class:
			text << "class : " << exp->name;
			if (exp->type.size())
				text << " inherit : " << exp->type;
			break;
		case Syntax_Decl:
			text << "decl : " << exp->type << ' ' << exp->name;
			if (const BString* path = exp->value)
				text << " = " << *path;
			break;
		case Syntax_Name:
			text << "name : " << exp->type << ' ' << exp->name;
			break;
		case Syntax_Self:
			text << "self : " << exp->type;
			if (exp->name.size())
				text << ' ' << exp->name;
			break;
		case Syntax_Function:
			text << "function : " << exp->type << " " << exp->name;
			break;
		case Syntax_Method:
			text << "method : ";
			if (exp->type.size())
				text << exp->type << " ";
			text << exp->name;
			break;
		case Syntax_Begin:
			level++;
			text << "begin";
			if (exp->value.valid())
				text << " $" << (int)exp->value;
			break;
		case Syntax_End:
			level--;
			text -= '\t';
			text << "end";
			if (exp->value.valid())
				text << " @" << (int)exp->value;
			break;
		case Syntax_Opera:
			text << "opera : " << exp->name;
			if (exp->type.size())
				text << " => " << exp->type;
			break;
		case Syntax_Call:
			text << "call : " << exp->name;
			if (exp->type.size())
				text << " => " << exp->type;
			break;
		case Syntax_If:
			text << "if @" << (int)exp->value;
			break;
		case Syntax_Else:
			text << "else @" << (int)exp->value;
			break;
		case Syntax_While:
			text << "while @" << (int)exp->value;
			break;
		case Syntax_Continue:
			text << "continue @" << (int)exp->value;
			break;
		case Syntax_Break:
			text << "break @" << (int)exp->value;
			break;
		case Syntax_For:
			text << "for @" << (int)exp->value;
			break;
		case Syntax_Switch:
			text << "switch @" << (int)exp->value;
			break;
		case Syntax_Case:
			text << "case : (" << exp->type << ')' << StringOfValue(exp->value);
			break;
		case Syntax_Return:
			text << "return @" << (int)exp->value;
			break;
		case Syntax_Value:
			text << "value : " << exp->type << ' ' << StringOfValue(exp->value);
			break;
		default:
			break;
		}
		text << '\n';
		this->addText(text);
	}
	_chunkTree.clear();
	for (int i = 0; i < exps.size(); i++)
	{
		const BExp* exp = exps[i];
		if (exp->syntax == Syntax_Begin)
		{
			auto it = _chunkTree.append();
			createChunk(it, i);
		}
	}
	for (int i = 0; i < _script->breakpointCount(); i++)
	{
		const BString& breakpoint = _script->breakpoint(i);
		int pos = breakpoint.find(':');
		if (pos == breakpoint.size())
		{
			int number = breakpoint.toInt();
			_breakLines.insert(number);
		}
	}
	ScriptFormat* format = (ScriptFormat*)this->format();
	format->setScript(_script);
	format->update(this->text());
	this->fresh();
}
void ScriptBrowser::drawErrorFlag(BPainter& painter, SourceFormat::Item* item)
{
	const BImage* image = painter.font()->image('~');
	int count = item->rect.width() / image->width();
	if (count < 1)
		count = 1;
	BRect lineRect = this->lineRect(item->line);
	BPoint pos(item->rect.left(), lineRect.bottom());
	pos.x() += (item->rect.width() - image->width() * count) / 2;
	painter.setColor(168, 0, 0);
	for (int i = 0; i < count; i++)
	{
		painter.drawImage(pos, image->size(), image);
		pos.x() += image->width();
	}
}

void ScriptBrowser::createChunk(ChunkTree::iterator& it, int& line)
{
	const BExpHolderArray& sequence = _script->sequence();
	it->begin = line++;
	while (line < sequence.size())
	{
		BExp* exp = sequence[line];
		if (exp->syntax == Syntax_End)
		{
			it->end = line;
			break;
		}
		if (exp->syntax == Syntax_Begin)
		{
			auto sub = it.append();
			createChunk(sub, line);
		}
		line++;
	}
}
void ScriptBrowser::freshChunk(ChunkTree::iterator& it)
{
	const BRect& beginRect = this->lineRect(it->begin);
	const BRect& endRect = this->lineRect(it->end);
	it->head.set(_chunkArea.left(), beginRect.top(), _chunkArea.width(), beginRect.height());
	it->head.move(it.level() * _chunkArea.width(), 0);
	it->head %= 60;
	it->tail.set(_chunkArea.left(), endRect.top(), _chunkArea.width(), endRect.height());
	it->tail.move(it.level() * _chunkArea.width(), 0);
	it->tail %= 60;
	it->rect = it->head;
	if (it->visible)
	{
		it->rect.expand(it->tail);
	}
	for (int i = 0; i < it.count(); i++)
	{
		auto sub = it.seek(i);
		freshChunk(sub);
	}
}
void ScriptBrowser::foldedChunk(ChunkTree::iterator& it)
{
	for (int i = it->begin + 1; i <= it->end; i++)
	{
		this->setLineVisible(i, it->visible);
	}
	if (it->visible)
	{
		for (auto sub = it.begin(); sub < it.end(); sub++)
		{
			foldedChunk(sub);
		}
	}
}
ScriptBrowser::ChunkTree::iterator ScriptBrowser::chunkUnder(const BPoint& point)
{
	for (auto it = _chunkTree.begin(); it.valid(); ++it)
	{
		auto res = chunkUnder(it, point);
		if (res.valid())
			return res;
	}
	return _chunkTree.end();
}
ScriptBrowser::ChunkTree::iterator ScriptBrowser::chunkUnder(ChunkTree::iterator& it, const BPoint& point)
{
	if (it->rect.contain(point))
	{
		return it;
	}
	if (it->visible)
	{
		for (int i = 0; i < it.count(); i++)
		{
			auto sub = it.seek(i);
			auto res = chunkUnder(sub, point);
			if (res.valid())
				return res;
		}
	}
	return it.end();
}
void ScriptBrowser::drawChunk(BPainter& painter, ChunkTree::iterator& it)
{
	if (it == _hoveredChunk)
	{
		painter.setColor(BColor(128, 156, 200, 128));
		painter.fillRect(it->rect);

		painter.setColor(BColor(28, 56, 128, 128));
		painter.drawRect(it->head);
		painter.drawGraph(it->head % 80, Graph_Plus);
	}
	painter.setColor(BColor(28, 56, 128, 128));
	painter.drawRect(it->head);
	if (it->visible)
	{
		painter.drawImage(it->head % 60, painter.font()->image('-'), true);
		painter.drawLine(it->head.bottomCenter(), it->tail.center());
		painter.drawLine(it->tail.center(), it->tail.rightCenter());
		for (int i = 0; i < it.count(); i++)
		{
			auto sub = it.seek(i);
			drawChunk(painter, sub);
		}
	}
	else
	{
		painter.drawImage(it->head % 60, painter.font()->image('+'), true);
	}
}

void ScriptBrowser::slotAssetChanged(BObject* object, const BValue& value)
{
	BScript* script = this->asset();
	if (_script != script)
	{
		if (_script)
			disconnect(_script);
		_script = script;
		if (_script)
			connect(_script, Signal_Compiled, &ScriptBrowser::slotScriptCompiled);
	}
	freshDocument();
}

void ScriptBrowser::slotTextEditFlushed(BObject* object, const BValue& value)
{
	if (this->query("code-rects", _codeRectsValue))
	{
		BRectArray* codeRects = _codeRectsValue;
		ScriptFormat* format = (ScriptFormat*)this->format();
		ScriptFormat::ItemMap& itemMap = format->itemMap();
		for (auto it = itemMap.begin(); it.valid(); it++)
		{
			int min = it->range.min();
			int max = it->range.max();
			it->rect = codeRects->at(min);
			for (int i = min + 1; i < max; i++)
			{
				it->rect.expand(codeRects->at(i));
			}
		}
	}
	for (int i = 0; i < _chunkTree.count(); i++)
	{
		auto it = _chunkTree.seek(i);
		freshChunk(it);
	}
}
void ScriptBrowser::slotScriptCompiled(BObject* object)
{
	freshDocument();
}

void ScriptBrowser::updateEvent(const BEvent& event)
{
	BTextEdit::updateEvent(event);
}
void ScriptBrowser::showEvent(const BEvent& event)
{
	BTextEdit::showEvent(event);
}
void ScriptBrowser::closeEvent(const BEvent& event)
{
	BTextEdit::closeEvent(event);
}

void ScriptBrowser::resizeEvent(const BEvent& event)
{
	BTextEdit::resizeEvent(event);
	BRect rect = this->rect(Part_Left);
	rect.move(rect.width(), 0);
	int half = this->margin(Part_Left) / 2;
	_breakArea.set(rect.x(), rect.y(), half, rect.height());
	_chunkArea.set(_breakArea.right(), rect.y(), half, rect.height());
}

void ScriptBrowser::styleEvent(const BEvent& event)
{
	BTextEdit::styleEvent(event);
	if (const BFont* font = this->font())
	{
		this->setMargin(Part_Left, font->size() * 2);
	}
	else
	{
		this->setMargin(Part_Left, 32);
	}
}
void ScriptBrowser::freshEvent(const BEvent& event)
{
	if (const BFont* font = this->font())
	{
		BString numberStr(this->lineCount());
		int left = font->textSize(numberStr).width();
		if (left < font->size())
			left = font->size();
		this->setPerch(Part_Left, left);
	}
	BTextEdit::freshEvent(event);
}
void ScriptBrowser::paintEvent(const BEvent& event)
{
	BTextEdit::paintEvent(event);

	BPainter painter(this);

	if (const BFont* font = painter.font())
	{
		BValue value;
		if (this->query("line-range", value))
		{
			BRange lineRange = value;
			BString numberText;
			const BRect& leftRect = this->rect(Part_Left);
			int x = leftRect.x();
			int w = leftRect.width();
			painter.setColor(Color_Text);
			for (int i = lineRange.min(); i < lineRange.max(); i++)
			{
				if (this->lineVisible(i))
				{
					const BRect& rect = this->lineRect(i);
					numberText.reset();
					numberText << i;
					painter.drawText(x, rect.y(), w, rect.height(), numberText, Align_Center);
				}
			}
		}
		if (this->query("input-line", value))
		{
			int line = value;
			if (line >= 0)
			{
				const BRect& inputRect = this->lineRect(line);
				painter.setColor(128, 128, 128, 128);
				painter.setLineWidth(2);
				painter.drawRect(inputRect);
			}
			painter.setLineWidth(1);
		}
	}

	for (int i = 0; i < _chunkTree.count(); i++)
	{
		auto it = _chunkTree.seek(i);
		drawChunk(painter, it);
	}

	painter.setColor(128, 128, 128, 64);
	painter.fillRect(_breakArea);

	painter.setColor(200, 128, 128);
	for (int i = 0; i < _breakLines.size(); i++)
	{
		int line = _breakLines[i];
		BRect rect = this->lineRect(line);
		rect.left() = _breakArea.left();
		rect.right() = _breakArea.right();
		rect.scale(rect.center(), 0.8);
		painter.drawGraph(rect, Graph_Circle_Fill);
	}
	const BExpHolderArray& sequence = _script->sequence();
	for (int i = 0; i < sequence.size(); i++)
	{
		if (!this->lineVisible(i))
			continue;
		const BExp* exp = sequence[i];
		if (exp->info.empty())
			continue;
		BRect rect = this->lineRect(i);
		painter.drawText(rect, exp->info, Align_RightCenter);
	}
}

void ScriptBrowser::mousePressEvent(const BMouseEvent& mouseEvent)
{
	BPoint point = mouseEvent.pos();
	if (_breakArea.contain(point))
	{
		int line = this->lineAt(point);
		if (line >= 0)
		{
			int index = _breakLines.find(line);
			if (_breakLines.check(index))
			{
				_breakLines.remove(index, 1);
				emit("remove-breakpoint", line);
			}
			else
			{
				_breakLines.insert(line);
				emit("create-breakpoint", line);
			}
			this->fresh();
		}
	}
	if (_hoveredChunk.valid())
	{
		_hoveredChunk->visible = !_hoveredChunk->visible;
		foldedChunk(_hoveredChunk);
	}
	BTextEdit::mousePressEvent(mouseEvent);
}
void ScriptBrowser::mouseReleaseEvent(const BMouseEvent& mouseEvent)
{
	BTextEdit::mouseReleaseEvent(mouseEvent);
}
void ScriptBrowser::mouseMoveEvent(const BMouseEvent& mouseEvent)
{
	BTextEdit::mouseMoveEvent(mouseEvent);
	BPoint pos = mouseEvent.pos();
	if (_breakArea.contain(pos))
	{
		this->setCursor(Cursor_Hand);
	}

	if (_script->breaked())
	{
		ScriptFormat::Item* item = 0;
		ScriptFormat* format = (ScriptFormat*)this->format();
		ScriptFormat::ItemMap& itemMap = format->itemMap();
		for (auto it = itemMap.begin(); it.valid(); it++)
		{
			if (it->rect.contain(pos))
			{
				item = &it.value();
				break;
			}
		}
		if (_hoveredItem != item)
		{
			_hoveredItem = item;
			if (item)
			{
				BString text;
				this->text().substr(text, item->range.min(), item->range.max());
				this->emit("popup-variant-button", text);
			}
		}
	}
	ChunkTree::iterator hoveredChunk = chunkUnder(pos);
	if (_hoveredChunk != hoveredChunk)
	{
		_hoveredChunk = hoveredChunk;
		Chunk& chunk = hoveredChunk.value();
		this->fresh();
	}
}
